// 迷你商店 插件
// 最后编辑时间：2025年2月24日20:11:54

// --------------------------------------------

//                  全局配置变量

// --------------------------------------------


dictionary shopItem = {
    // 商品名（不要增减、调换顺序，因为跟代码一一对应）,复活玩家的价格是复活一个玩家的价格
    {"shopItemName",array<string> = {"抗病毒血清","急救包","自我恢复","除颤器","复活自己","复活全部玩家"}},
    // 对应价格（可以修改）
    {"shopItemPrice",array<int> = {250,500,500,500,500,1250}},
    // 下面这句不要修改
    {"shopItemWeapon",array<string> = {"inoculator","firstaid","selfrev","defibrillator",""}}
};

// 下面是设置各种类型僵尸被杀会加多少钱
dictionary zombieScore = {
    // 普通僵尸
    {"zombie",1},
    // 普通僵尸，但是带头盔
    {"hasHelmet",2},
    // 防爆
    {"riot",3},
    // 医生
    {"doctor",1},
    // 背包客
    {"looter",1},
    // 不知道是啥，跟普通僵尸一样
    {"runner",1},
    // 黄裤子
    {"carrier",5},
    // 盾牌
    {"charger",4},
    // 尖叫
    {"screamer",5},
    // 尖叫新娘
    {"screamer_bride",5}
};


// --------------------------------------------

//                  全局中转变量（不要修改）

// --------------------------------------------

// 数据库连接
CSQLConnection@ gMainConnection = null;

// --------------------------------------------

//                  初始化

// --------------------------------------------

string author = "Live";
string pluginName = "MiniShop 迷你商店-插件";
string version = "0.1";

// 插件初始化
void OnPluginInit()
{
    // 设置插件信息
    PluginData::SetAuthor(author);
    PluginData::SetName(pluginName);
    PluginData::SetVersion(version);

    // 加载提示
    ThePresident.InfoFeed("插件:"+pluginName+"| 版本:"+version+"| 作者:"+author,false);

    // 当玩家说
    Events::Player::PlayerSay.Hook( @PlayerSay );
    // 玩家执行菜单命令
    Events::Player::OnMenuExecuted.Hook( @OnMenuExecuted );
    // 感染者死亡
    Events::Infected::OnInfectedKilled.Hook( @OnInfectedKilled );
    // 当玩家连接后
    Events::Player::OnPlayerConnected.Hook( @OnPlayerConnected );
    // 当僵尸出生后
    Events::Infected::OnInfectedSpawned.Hook( @OnInfectedSpawned );

    // 连接数据库
    ConnectToDB();

    // 地图开始
    ThePresident_OnMapStart();

}

// --------------------------------------------

//                  数据库

// --------------------------------------------

// 连接数据库
void ConnectToDB()
{
    // SQL Connection info
    string strSQLHost = "localhost"; // IP地址
    int iPort = 0; // 端口，0就是默认mysql端口3306
    string strSQLUser = "contagion"; // 用户名
    string strSQLPass = "_xLk&ZF4p*oL!R&#s"; // 密码
    string strSQLDB = "contagion"; // 数据库名

    // Create our connection
    SQL::Connect( strSQLHost, iPort, strSQLUser, strSQLPass, strSQLDB, OnSQLConnect );
}

void OnSQLConnect( CSQLConnection@ pConnection )
{
    if ( pConnection.Failed() ) {
        ThePresident.InfoFeed("数据库连接失败！",false);
    }
    
    @gMainConnection = pConnection;
    ThePresident.InfoFeed("数据库连接成功！",false);
    Chat.PrintToConsole(all, "{green}数据库连接成功！");
    Log.PrintToServerConsole(LOGTYPE_INFO, "Database connected...");
    // 同步所有玩家金币
    synchronizeAllPlayerMoney();
}

// 当插件卸载
void OnPluginUnload()
{
    // 关闭数据库连接
    SQL::Disconnect(gMainConnection);
}

// 地图关闭时关闭连接
void OnMapShutdown()
{
    // saveALLMoney();
	SQL::Disconnect( gMainConnection );
}

// 地图初始化时，重新连接
void OnMapInit()
{
	@gMainConnection = null;
	ConnectToDB();
}

// --------------------------------------------

//                  钩子函数

// --------------------------------------------

// 当玩家说话
HookReturnCode PlayerSay(CTerrorPlayer@ pPlayer, CASCommand@ pArgs)
{
	string arg = pArgs.Arg( 1 );
	string arg1 = "";
	string arg2 = "";

	// 切分字符串(这样就可以用空格传一个命令参数了)
	int subIndex = arg.findFirst(" ",0);
	if(not (subIndex < 0)){
		arg1 = arg.substr(0,subIndex);
		arg2 = arg.substr(subIndex+1,-1);
		//Chat.PrintToChat(all,""+arg1+"|"+arg2);
	}else{
		arg1 = arg;
	}

    // 商店
    if( Utils.StrEql( arg1, "!b" ) || Utils.StrEql( arg1, "!buy" ) ){
        Menu pMenu;
        pMenu.SetID( "SHOP" );
        pMenu.SetTitle( "商店" );
        fillItem(pPlayer,pMenu,'shopItemName');
        Chat.HintMessagePlayer(pPlayer,"当前金币为："+pPlayer.GetEntityInfoInt("money"),Color("#ffffff"));
        return HOOK_HANDLED;
    }else if( arg1 == "!tb" && AdminSystem.AdminExist(pPlayer)){
        synchronizeAllPlayerMoney();
		return HOOK_HANDLED;
    }
    return HOOK_CONTINUE;
}

// 菜单填充
void fillItem(CTerrorPlayer@ pPlayer,Menu pMenu,string menuName){
    // 读取商品，遍历添加商品
    array<string> shopItemName = cast<array<string>>(shopItem[menuName]);
    array<int> shopItemPrice = cast<array<int>>(shopItem["shopItemPrice"]);
    for(uint i = 0; i < shopItemName.length(); i++){
        //Chat.PrintToChat(all,shopItemName[i]);
        pMenu.AddItem(shopItemName[i]+" - "+shopItemPrice[i]);
    }
    pMenu.Display( pPlayer, -1 );
}

// 当玩家菜单执行
HookReturnCode OnMenuExecuted(CTerrorPlayer@ pPlayer, const string &in szID, int &in iValue)
{
    if( Utils.StrEql( szID, "SHOP" ) ){
        array<string> shopItemWeapon = cast<array<string>>(shopItem['shopItemWeapon']);
        if( iValue == 6 ){
            // 复活队友
            array<int> playerList = Utils.CollectPlayers();
            for(uint i=0;i<playerList.length();i++){
                CTerrorPlayer@ player = ToTerrorPlayer(playerList[i]);
                if(not (player.GetTeamNumber() == TEAM_SURVIVOR)){
					int money = pPlayer.GetEntityInfoInt("money")-cast<array<int>>(shopItem["shopItemPrice"])[iValue-1];
					if( money > 0 ){
						// 扣除玩家金币
						pPlayer.SetEntityInfo("money",money);
						// 修改被复活玩家的游戏阵营
						player.ChangeTeam( TEAM_SURVIVOR );
						// 复活玩家
						player.Respawn();
						// 传送到买复活的玩家
						player.SetAbsOrigin(pPlayer.GetAbsOrigin());
						Chat.PrintToChat(all,"{green}"+pPlayer.GetPlayerName()+"{white}花费金币复活了玩家{green}"+player.GetPlayerName());
					}else{
						Chat.PrintToChat(pPlayer,"{hotpink}购买失败，金币不足");
						break;
					}
                   
                }
            }
        }else if( iValue == 5 ){
            Open_PlayerShop_Tele(pPlayer);
        }else{
			int money = pPlayer.GetEntityInfoInt("money")-cast<array<int>>(shopItem["shopItemPrice"])[iValue-1];
			if(money > 0){
				pPlayer.GiveWeapon(shopItemWeapon[iValue-1]);
				// 扣除玩家金币
				pPlayer.SetEntityInfo("money",money);
				Chat.PrintToChat(pPlayer,"{hotpink}购买成功");
				Chat.HintMessagePlayer(pPlayer,"当前金币为："+pPlayer.GetEntityInfoInt("money"),Color("#ffffff"));
			}else{
				Chat.PrintToChat(pPlayer,"{hotpink}购买失败，金币不足");
			}
        }
    }else if(Utils.StrContains("Menu_PlayerShop_Tele_Select", szID)){
        /******************************************
                        复活菜单
    *******************************************/
        // 得到下标
        CASCommand@ arg = StringToArgSplit(szID, ";");
        int index =  Utils.StringToInt(arg.Arg(1));
        if(iValue == 8){
            // 如果是0则第一页，返回就是返回玩家商店
            if( index != 0 )
                Menu_PlayerShop_Tele(pPlayer,index-7);
        }else if(iValue == 9){
            Menu_PlayerShop_Tele(pPlayer,index+7);
        }else if(iValue>0 && iValue<8){
            iValue = iValue + index;
            if( pPlayer.GetTeamNumber() != TEAM_SURVIVOR ){
                int pPlayerIndex = playerList[iValue-1];
                if(pPlayerIndex == pPlayer.entindex()){
                    Chat.PrintToChat(pPlayer,"{hotpink}请选择除自己之外的人");
                }else{
					CBasePlayer@ telePlayer = ToBasePlayer(pPlayerIndex);
					// 判断所选择的玩家是否是人类
					if(telePlayer.GetTeamNumber() == TEAM_SURVIVOR){
                        // 扣钱
                        int money = pPlayer.GetEntityInfoInt("money")-cast<array<int>>(shopItem["shopItemPrice"])[iValue-1];
                        if( money > 0 ){
                            pPlayer.SetEntityInfo("money",money);
                            // 修改被复活玩家的游戏阵营
                            pPlayer.ChangeTeam( TEAM_SURVIVOR );
                            // 复活玩家
                            pPlayer.Respawn();
                            // 得到被选择玩家的位置
                            Vector teleV = telePlayer.GetAbsOrigin();
                            // 将复活玩家传送过去
                            pPlayer.SetAbsOrigin(teleV);
                            Chat.PrintToChat(all,"{hotpink}"+pPlayer.GetPlayerName()+"复活了自己到"+telePlayer.GetPlayerName());
                        }else{
                            Chat.PrintToChat(pPlayer,"{hotpink}金币不足");
                        }
                        return HOOK_CONTINUE;
					}else{
						Chat.PrintToChat(pPlayer,"{hotponk}请选择一个人类");
					}
                }
            }else{
                Chat.PrintToChat(pPlayer,"{hotpink}人死才能复生");
            }
            Open_PlayerShop_Tele(pPlayer);
        }
    }
	// 数据库保存玩家金币
	saveMoney( pPlayer );
    return HOOK_CONTINUE;
}

// 感染者死亡
HookReturnCode OnInfectedKilled(Infected@ pInfected, CTakeDamageInfo &in DamageInfo)
{

    CBaseEntity@ pAttacker = DamageInfo.GetAttacker();	//获取攻击者信息
    CTerrorPlayer@ pTerror = ToTerrorPlayer(pAttacker);	//转换攻击者为玩家属性

    int score = 0;

    switch( pInfected.GetZombieID() ){
        case ZID_NRM :{
            //Chat.PrintToChat(pTerror,"击杀普通僵尸");
            if( pInfected.HasRiotHelmet() ){
                score = int(zombieScore["hasHelmet"]);
            }else{
                score = int(zombieScore["zombie"]);
            }
            break;
        }
        case ZID_RIOT:{
            //Chat.PrintToChat(pTerror,"击杀普通防爆");
            score = int(zombieScore["riot"]);
            break;
        }
        case ZID_DOCTOR:{
            //Chat.PrintToChat(pTerror,"击杀医生僵尸");
            score = int(zombieScore["doctor"]);
            break;
        }
        case ZID_LOOTER:{
            //Chat.PrintToChat(pTerror,"击杀背包客僵尸");
            score = int(zombieScore["looter"]);
            break;
        }
        case ZID_RUNNER:{
            //Chat.PrintToChat(pTerror,"击杀runner");
            score = int(zombieScore["runner"]);
            break;
        }
        case ZID_BOSS_CARRIER:{
            //Chat.PrintToChat(pTerror,"击杀黄裤子");
            score = int(zombieScore["carrier"]);
            break;
        }
        case ZID_BOSS_CHARGER:{
            //Chat.PrintToChat(pTerror,"击杀盾牌哥");
            score = int(zombieScore["charger"]);
            break;
        }
        case ZID_BOSS_SCREAMER:{
            //Chat.PrintToChat(pTerror,"击杀尖叫");
            score = int(zombieScore["screamer"]);
            break;
        }  
        case ZID_BOSS_SCREAMER_BRIDE:{
            //Chat.PrintToChat(pTerror,"击杀新娘");
            score = int(zombieScore["screamer_bride"]);
            break;
        }
    }
    pTerror.SetEntityInfo("money",pTerror.GetEntityInfoInt("money")+score);
    saveMoney(pTerror);
    // Chat.HintMessagePlayer(pTerror,"当前金币为："+pTerror.GetEntityInfoInt("money"),Color("#ffffff"));

	return HOOK_CONTINUE;
}

// 当玩家连接后
HookReturnCode OnPlayerConnected(CTerrorPlayer@ pPlayer)
{
    // 数据库添加玩家
    DB_addPlayer(pPlayer);
    return HOOK_CONTINUE;
}

// --------------------------------------------

//                  数据库同步玩家信息

// --------------------------------------------


void DB_addPlayer(CTerrorPlayer@ pPlayer){
    string steamid = Utils.Steam64ToSteam32(pPlayer.GetSteamID64());
    // 创建数据库信息
    string sql_addPlayer = "INSERT INTO `record`(steamid,name) "
    "SELECT '"+steamid+"','"+pPlayer.GetPlayerName()+"' FROM DUAL "
    "WHERE NOT EXISTS("
	"SELECT steamid "
	"FROM `record` "
	"WHERE steamid = '"+steamid+"'"
    ");";
    //Chat.PrintToChat(all,sql_addPlayer);
    SQL::SendQuery( gMainConnection, sql_addPlayer, addPlayer );

    // 设置玩家金币，同步
    string sql_queryPlayer = "select * from `record` where steamid = '"+steamid+"';";
    SQL::SendQuery( gMainConnection, sql_queryPlayer, setPlayerMoney );
}

void addPlayer(IMySQL@ pQuery){
    // If our query is null, then it failed.
    if ( pQuery is null ) return;

    // That means the table does not exist. Let's create our table then.
    if ( !pQuery.Failed() )
    {
        //Chat.PrintToChat(all,"添加玩家信息成功！");
    }else{
        Chat.PrintToChat(all,"{red}警告：有玩家信息添加失败！");
    }
}

void setPlayerMoney(IMySQL@ pQuery){
    if( pQuery is null ) return;
    if( !pQuery.Failed() ){
        string steamid = SQL::ReadResult::GetString( pQuery, "steamid" );
        CTerrorPlayer@ pPlayer = GetPlayerBySteamID(steamid);
        int money = SQL::ReadResult::GetInt( pQuery, "money" );
        // 设置玩家金币
        pPlayer.SetEntityInfo( "money", money);
        Log.PrintToServerConsole(LOGTYPE_INFO, "money---Synchronized");
        // Chat.PrintToChat(all,"金币同步完毕");
    }
}

//僵尸出生后
HookReturnCode OnInfectedSpawned(Infected@ pInfected)   
{
    // pInfected.SetAnimationSet(10);
    return HOOK_CONTINUE;
}

// --------------------------------------------

//                  函数实现

// --------------------------------------------

// 同步所有玩家金币
void synchronizeAllPlayerMoney(){
    array<int> playerList = Utils.CollectPlayers();
    for(uint i = 0;i < playerList.length(); i++){
        CTerrorPlayer@ player = ToTerrorPlayer(playerList[i]);
        DB_addPlayer(player);
    }
    Chat.PrintToChat(all,"{hotpink}自动同步所有玩家金币");
}


// 保存玩家金币
void saveMoney(CTerrorPlayer@ pPlayer){
    string steamid = Utils.Steam64ToSteam32(pPlayer.GetSteamID64());
    string sql_saveMoney = "update `record` set money = "+pPlayer.GetEntityInfoInt("money")+" where steamid = '"+steamid+"';";
    SQL::SendQuery( gMainConnection, sql_saveMoney);
}

// --------------------------------------------

//                  生命周期

// --------------------------------------------

// 地图开始
void ThePresident_OnMapStart(){
    ThePresident_OnRoundStart();
}

// 回合开始
void ThePresident_OnRoundStart()
{
    // 开启除颤复活
    Engine.RunConsoleCommand( "survivor_allow_defib 1");
}

// --------------------------------------------

//                  玩家复活菜单

// --------------------------------------------

array<int> playerList;

void Open_PlayerShop_Tele(CTerrorPlayer@ pPlayer){
    Menu_PlayerShop_Tele(pPlayer,0);
}

// 玩家传送菜单
void Menu_PlayerShop_Tele(CTerrorPlayer@ pPlayer,int index){
    if (pPlayer is null ) return;
    playerList = Utils.CollectPlayers();
    Menu pMenu;
	pMenu.SetID( "Menu_PlayerShop_Tele_Select;"+index );
	pMenu.SetTitle( "@请选择复活到谁@" );
    // 内容填充方法（填充玩家）
    Player_ItemFill(pPlayer,pMenu,index);
}

// 内容填充方法，从index开始
void Player_ItemFill(CTerrorPlayer@ pPlayer,Menu pMenu,int index){
    // 计数
    int count = 0;
    for(uint i=index;i<playerList.length();i++){
        CBasePlayer@ player = ToBasePlayer(playerList[i]);
        pMenu.AddItem(""+player.GetPlayerName());
        // 因为菜单一页只能有七个
        if(count>6) break;
        count++;
    }
    pMenu.SetBack( true );
    // 如果大于7项，就有下一页按钮
    if(count>6){
        pMenu.SetNext( true );
    }
    pMenu.Display( pPlayer, -1 );
}
